Week 14 - STEM 691

Interactive maps with R

Joshua Rosenberg and Isabella Velásquez

Agenda

  • Final projects
  • Interactive maps
  • Revisit static maps
  • Interactive maps with plotly
  • Interactive maps with leaflet

Final projects

Rubric

Given a whole semesters’ worth of data viz know-how, how would you assess a data visualization? Which principles and practices would you want to see employed?

We’ve come up with a starting point:

https://bit.ly/stem691-rubric

Small group discussion

In small groups in a breakout room, please discuss 3-5 things that you would like to either change or modify. You can make suggestions related to the Major Categories, Expectations, or Point Value.

Discuss for five minutes and return to the whole group with a plan for how to share.

Process

For next week, you will need to develop up to three slides. Here are the requirements:

  • Presenters should discuss the story of the data, using the visualizations as aids.
  • Your presentation should be no more than 3 slides
  • Your presentation should take 3 minutes or fewer
  • Presenters should only speak for 1-2 minutes and take time to answer any questions or interact with the other attendees who appear in their booth.
  • This presentation style mimics actual poster presentations that occur at many conferences.

Process

You can use Google Slides, Powerpoint, Keynote, or another tool.

To ensure you receive full credit, please upload or share a link to your presentation by 9:00 am on Wednesday, May 1 for the Final Project assignment:

https://utk.instructure.com/courses/196893/assignments/1779345

Interactive maps in R

Interactivity

  • New York Times graphics editor Gregor Aisch noted that only 10 to 15 percent of readers who visit an interactive visualization on their site actually click on anything.
  • Is it worth the time and effort to make these things?

As with most design-related things, it depends on the goals and the audience of your visualization.

Interactivity

  • Immersion
  • Flexibility
  • Zooming, hover-over, pop-ups

Packages for interactive maps in R

  • plotly + ggplot
  • leaflet

Other packages include:

  • ggigraph + ggplot
  • highcharter

Revisit static maps

Species Occurrence data

The rgbif package accesses data from the Global Biodiversity Information Facility (GBIF).

# install.packages("rgbif"
library(tidyverse)
library(rgbif)

Species Occurrence data

Let’s say we want to download data on the Monarch butterfly 🦋

dan_ple <- 
  rgbif::occ_data(scientificName = 'Danaus plexippus')

dan_ple_dat <- dan_ple$data
glimpse(dan_ple_dat)
Rows: 500
Columns: 83
$ key                           <chr> "4507686934", "4507704024", "4507984043"…
$ scientificName                <chr> "Danaus plexippus (Linnaeus, 1758)", "Da…
$ decimalLatitude               <dbl> -34.90042, -34.86286, -26.45418, 28.4724…
$ decimalLongitude              <dbl> 138.69976, 139.39130, 152.63339, -16.253…
$ issues                        <chr> "cdc,cdround", "cdc,cdround", "cdc,cdrou…
$ datasetKey                    <chr> "50c9509d-22c7-4a22-a47d-8c48425ef4a7", …
$ publishingOrgKey              <chr> "28eb1a3f-1c15-4a95-931a-4af90ecb574d", …
$ installationKey               <chr> "997448a8-f762-11e1-a439-00145eb45e9a", …
$ hostingOrganizationKey        <chr> "28eb1a3f-1c15-4a95-931a-4af90ecb574d", …
$ publishingCountry             <chr> "AU", "AU", "AU", "US", "US", "US", "US"…
$ protocol                      <chr> "DWC_ARCHIVE", "DWC_ARCHIVE", "DWC_ARCHI…
$ lastCrawled                   <chr> "2024-04-21T08:18:21.750+00:00", "2024-0…
$ lastParsed                    <chr> "2024-04-21T17:35:12.192+00:00", "2024-0…
$ crawlId                       <int> 451, 451, 451, 451, 451, 451, 451, 451, …
$ basisOfRecord                 <chr> "HUMAN_OBSERVATION", "HUMAN_OBSERVATION"…
$ occurrenceStatus              <chr> "PRESENT", "PRESENT", "PRESENT", "PRESEN…
$ lifeStage                     <chr> "Larva", "Adult", "Larva", "Adult", "Adu…
$ taxonKey                      <int> 5133088, 5133088, 5133088, 5133088, 5133…
$ kingdomKey                    <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1…
$ phylumKey                     <int> 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, …
$ classKey                      <int> 216, 216, 216, 216, 216, 216, 216, 216, …
$ orderKey                      <int> 797, 797, 797, 797, 797, 797, 797, 797, …
$ familyKey                     <int> 7017, 7017, 7017, 7017, 7017, 7017, 7017…
$ genusKey                      <int> 5133087, 5133087, 5133087, 5133087, 5133…
$ speciesKey                    <int> 5133088, 5133088, 5133088, 5133088, 5133…
$ acceptedTaxonKey              <int> 5133088, 5133088, 5133088, 5133088, 5133…
$ acceptedScientificName        <chr> "Danaus plexippus (Linnaeus, 1758)", "Da…
$ kingdom                       <chr> "Animalia", "Animalia", "Animalia", "Ani…
$ phylum                        <chr> "Arthropoda", "Arthropoda", "Arthropoda"…
$ order                         <chr> "Lepidoptera", "Lepidoptera", "Lepidopte…
$ family                        <chr> "Nymphalidae", "Nymphalidae", "Nymphalid…
$ genus                         <chr> "Danaus", "Danaus", "Danaus", "Danaus", …
$ species                       <chr> "Danaus plexippus", "Danaus plexippus", …
$ genericName                   <chr> "Danaus", "Danaus", "Danaus", "Danaus", …
$ specificEpithet               <chr> "plexippus", "plexippus", "plexippus", "…
$ taxonRank                     <chr> "SPECIES", "SPECIES", "SPECIES", "SPECIE…
$ taxonomicStatus               <chr> "ACCEPTED", "ACCEPTED", "ACCEPTED", "ACC…
$ iucnRedListCategory           <chr> "LC", "LC", "LC", "LC", "LC", "LC", "LC"…
$ dateIdentified                <chr> "2024-01-01T01:12:40", "2024-01-01T04:19…
$ coordinateUncertaintyInMeters <dbl> 7, 3, 29845, 4, 5, NA, 11, NA, NA, NA, 4…
$ continent                     <chr> "OCEANIA", "OCEANIA", "OCEANIA", "AFRICA…
$ stateProvince                 <chr> "South Australia", "South Australia", "Q…
$ year                          <int> 2024, 2024, 2024, 2024, 2024, 2024, 2024…
$ month                         <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1…
$ day                           <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1…
$ eventDate                     <chr> "2024-01-01T11:41:07", "2024-01-01T11:11…
$ startDayOfYear                <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1…
$ endDayOfYear                  <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1…
$ modified                      <chr> "2024-01-01T03:35:31.000+00:00", "2024-0…
$ lastInterpreted               <chr> "2024-04-21T17:35:12.192+00:00", "2024-0…
$ references                    <chr> "https://www.inaturalist.org/observation…
$ license                       <chr> "http://creativecommons.org/licenses/by-…
$ isSequenced                   <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE…
$ isInCluster                   <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE…
$ datasetName                   <chr> "iNaturalist research-grade observations…
$ recordedBy                    <chr> "Graham Armstrong", "sarcastrophe", "Jac…
$ identifiedBy                  <chr> "Graham Armstrong", "sarcastrophe", "Jac…
$ geodeticDatum                 <chr> "WGS84", "WGS84", "WGS84", "WGS84", "WGS…
$ class                         <chr> "Insecta", "Insecta", "Insecta", "Insect…
$ countryCode                   <chr> "AU", "AU", "AU", "ES", "US", "US", "US"…
$ gbifRegion                    <chr> "OCEANIA", "OCEANIA", "OCEANIA", "EUROPE…
$ country                       <chr> "Australia", "Australia", "Australia", "…
$ publishedByGbifRegion         <chr> "OCEANIA", "OCEANIA", "OCEANIA", "NORTH_…
$ rightsHolder                  <chr> "Graham Armstrong", "sarcastrophe", "Jac…
$ identifier                    <chr> "195422818", "195435992", "195447354", "…
$ `http://unknown.org/nick`     <chr> "grahamarmstrong", "sarcastrophe", "arch…
$ verbatimEventDate             <chr> "2024-01-01 11:41:07", "2024-01-01 11:11…
$ verbatimLocality              <chr> "Woodforde SA 5072, Australia", "Pellari…
$ collectionCode                <chr> "Observations", "Observations", "Observa…
$ gbifID                        <chr> "4507686934", "4507704024", "4507984043"…
$ occurrenceID                  <chr> "https://www.inaturalist.org/observation…
$ taxonID                       <chr> "48662", "48662", "48662", "48662", "486…
$ catalogNumber                 <chr> "195422818", "195435992", "195447354", "…
$ institutionCode               <chr> "iNaturalist", "iNaturalist", "iNaturali…
$ eventTime                     <chr> "11:41:07+10:30", "11:11:32+10:30", "16:…
$ `http://unknown.org/captive`  <chr> "wild", "wild", "wild", "wild", "wild", …
$ identificationID              <chr> "439486498", "439524139", "439552350", "…
$ informationWithheld           <chr> NA, NA, "Coordinate uncertainty increase…
$ occurrenceRemarks             <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ infraspecificEpithet          <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ sex                           <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ distanceFromCentroidInMeters  <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ name                          <chr> "Danaus plexippus (Linnaeus, 1758)", "Da…

Download the world map data from rnaturalearth

The package rnaturalearth provides a map of countries of the entire world.

  • Use ne_countries to pull country data and choose the scale (rnaturalearthhires is necessary for scale = “large”)
  • Return the simple feature (sf) standard of data
# install.packages("rnaturalearth")
# install.packages("rnaturalearthdata")
library(rnaturalearth)

world <- ne_countries(scale = "medium", returnclass = "sf")
class(world)
[1] "sf"         "data.frame"

Making a static map: base

First, let’s start with creating a base map of the world using ggplot2’s geom_sf().

library(ggplot2)
theme_set(theme_minimal())

ggplot(data = world) +
    geom_sf()

Making a static map: aesthetics

Remember that sf geometries are no different than regular geometries, and can be displayed with the same level of control on their attributes:

ggplot(data = world) + 
    geom_sf(color = "white", 
            linewidth = 0.1,
            fill = "#0C1337")

Making a static map: layer data

We can layer the Monarch butterfly data from dan_ple_dat:

  • Add a new points geometry with geom_point()
  • Specify the data (since it differs from world) with data
  • Map longitude/latitude to the data with aes()
    • Variables are called decimalLongitude / decimalLatitude

Making a static map: layer data

ggplot(data = world) + 
    geom_sf(color = "white", 
            linewidth = 0.1,
            fill = "#0C1337") +
  geom_point(data = dan_ple_dat,
             aes(x = decimalLongitude,
                 y = decimalLatitude),
             color = "#FFD580")

Making a static map: layer data

Making a static map: aesthetics

We can layer edit the points according to life stage (lifeStage) by specifying color in aes().

Note

Anything not mapped to the data can be put outside of aes()

Making a static map: aesthetics

ggplot(data = world) + 
    geom_sf(color = "white",
            linewidth = 0.1,
            fill = "#0C1337") +
  geom_point(data = dan_ple_dat,
             aes(x = decimalLongitude,
                 y = decimalLatitude,
                 color = lifeStage))

Making a static map: aesthetics

Adding interactivity

ggplot2 + plotly

plotly is an R package for creating interactive web-based graphs via the open source JavaScript graphing library plotly.js.

# install.packages("plotly")
library(plotly)

ggplot2 + plotly

Use the function ggplotly() to draw the graph with plotly.js.

  • Printing the Plotly object will render the chart locally in your web browser or in the RStudio viewer

ggplot2 + plotly

Wrap your plot p in ggplotly():

p <- ggplot(data = world) +
  geom_sf(color = "white",
          linewidth = 0.1,
          fill = "#0C1337") +
  geom_point(
    data = dan_ple_dat,
    aes(x = decimalLongitude,
        y = decimalLatitude,
        color = lifeStage),
    size = 0.5
  )

ggplotly(p)

ggplot2 + plotly

Code along

Leaftlet

Leaflet is an open-source JavaScript library for interactive maps.

  • Simplicity, performance, and usability
  • Lots of plugins and documentation

The leaflet package

The leaflet R package makes it easy to create Leaflet maps from R.

# install.packages("leaflet")
library(leaflet)

The leaflet package: base map

Create a base map with addTiles().

leaflet() %>% addTiles()

The leaflet package: adding components

The different components of the map can be added using the pipe operator %>%.

However, some operations require the use of base R.

dplyr

mtcars %>% 
  pull(hp)

base R

mtcars$hp

The leaflet package: provider tiles

Specify different tile options with addProviderTiles

leaflet() %>% 
  addProviderTiles(providers$Esri.NatGeoWorldMa)

The leaflet package: zoom

leaflet() %>% 
  addProviderTiles(providers$Esri.NatGeoWorldMa) %>% 
  setView(lng = -83.9294, lat = 35.9546, zoom = 14)

The leaflet package: markers and tooltips

We can do things like add markers and tooltips.

leaflet() %>%
  addProviderTiles(providers$Esri.NatGeoWorldMa) %>%
  setView(lng = -83.9294, lat = 35.9546, zoom = 14) %>%
  addMarkers(lng = -83.9294, lat = 35.9546,
             popup = "<a href='https://www.utk.edu/'>University of Tennessee, Knoxville</a>")

The leaflet package: bringing in data

Add data when initializing the leaflet() function

  • ~ tells leaflet to look at the data
leaflet(dan_ple_dat) %>%
  addProviderTiles(providers$Esri.NatGeoWorldMa) %>%
  setView(lng = -118.259,
          lat = 34.0507666,
          zoom = 6) %>% 
  addCircles(
    ~ decimalLongitude,
    ~ decimalLatitude,
    popup =  ~ scientificName
  ) 

The leaflet package: bringing in data

The leaflet package: customizing maps

leaflet(dan_ple_dat) %>%
  addProviderTiles(providers$Esri.NatGeoWorldMa) %>%
  setView(lng = -118.259,
          lat = 34.0507666,
          zoom = 6) %>% 
  addCircles(
    ~ decimalLongitude,
    ~ decimalLatitude,
    popup =  ~ scientificName,
    weight = 9,
    radius = 40,
    color = "#ffa500",
    stroke = TRUE,
    fillOpacity = 0.8
  ) 

The leaflet package: customizing maps

The leaflet package: adding polygons

Specify a color scale for your polygons with fillColor.

leaflet(data = world) %>%
  addTiles() %>%
  addPolygons(
    fillColor = topo.colors(10, alpha = NULL),
    stroke = FALSE,
    label = ~ name
  )

The leaflet package: adding polygons

The leaflet package: making a choropleth

We have current GDP in U.S. dollars in the world data:

world %>% 
  select(name, gdp_md) %>% 
  head()
Simple feature collection with 6 features and 2 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -73.36621 ymin: -22.40205 xmax: 109.4449 ymax: 41.9062
Geodetic CRS:  WGS 84
       name gdp_md                       geometry
1  Zimbabwe  21440 MULTIPOLYGON (((31.28789 -2...
2    Zambia  23309 MULTIPOLYGON (((30.39609 -1...
3     Yemen  22581 MULTIPOLYGON (((53.08564 16...
4   Vietnam 261921 MULTIPOLYGON (((104.064 10....
5 Venezuela 482359 MULTIPOLYGON (((-60.82119 9...
6   Vatican    -99 MULTIPOLYGON (((12.43916 41...

The leaflet package: making a choropleth

Specify a color palette and a domain:

pal <- colorNumeric("YlOrRd",
                    domain = world$gdp_md)

The leaflet package: making a choropleth

Add the colors to your polygons by using pal and the variable you are using to fill the polygons (gdp_md).

  • ~ tells leaflet to look at the data
leaflet(data = world) %>%
  addTiles() %>%
  setView(lng = -118.259,
          lat = 34.0507666,
          zoom = 2) %>% 
  addPolygons(
    fillColor = ~ pal(gdp_md),
    stroke = FALSE
  )

The leaflet package: making a choropleth

The leaflet package: add labels

labels <- paste0(
  "<strong>", world$name, "</strong><br/>GDP: ", world$gdp_md) %>% lapply(htmltools::HTML)

leaflet(data = world) %>%
  addTiles() %>%
  setView(lng = -118.259,
          lat = 34.0507666,
          zoom = 2) %>% 
  addPolygons(
    fillColor = ~ pal(gdp_md),
    stroke = FALSE,
    label = labels
  )

The leaflet package: add labels

Code along

What’s next?

Assignment 12 will walk you through creating a Leaflet map.

  • Please reach out with any issues!

Final projects!